/**
 * Copyright (c) 2009 blurredVision
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
 * associated documentation files (the "Software"), to deal in the Software without restriction, 
 * including without limitation the rights to use, copy, modify, merge, publish, distribute, 
 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or 
 * substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 
 * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 
 * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
#include "StdAfx.h"
#include "Audio_OpenAL.h"
#include "SoundEffect_OpenAL.h"
#include "IRenderer.h"
#include "SoundBuffer_OpenAL.h"
#include "StreamingSound_OpenAL.h"
#include "StreamingBuffer_OpenAL.h"
#include "JukeBox_OpenAL.h"


#include <vector>
#include <stdio.h>
#include <vorbis/vorbisfile.h>
#include "vorbis/codec.h"

#include "Config.h"

namespace openAL {

Audio_OpenAL::Audio_OpenAL(void) :
	IAudio(),
	m_pContext(NULL),
	m_pDevice(NULL),
	m_DeviceName(""),
	m_iNumberOfSources(0),
	m_iFreeSources(0),
	m_b3D_ENABLED(false)	
{

}

Audio_OpenAL::~Audio_OpenAL(void)
{

}


void Audio_OpenAL::Destroy() {
	ALCcontext *m_pContext;
	ALCdevice *pDevice;

	destroyAllBuffers();

	// clear Sourceobjects
	std::map<ALuint, SourceObject*>::iterator it;
	it = m_mSourceObjects.begin();
	while (it != m_mSourceObjects.end()) {
		alDeleteSources(1, &it->second->sourceID);		
		it = m_mSourceObjects.erase(it);
	}

	m_pContext = alcGetCurrentContext();
	pDevice = alcGetContextsDevice(m_pContext);
	
	alcMakeContextCurrent(NULL);
	alcDestroyContext(m_pContext);
	alcCloseDevice(pDevice);

	delete this;
}

bool Audio_OpenAL::init(bool b3D) {
	int maxNumberOfSources = 15;
	
	m_b3D_ENABLED = b3D;	
	
	bool bReturn = false;

	// fill the device list
	getAllSoundDevices();

	// Eventuell steht schon ein Name in der Config:
	if (coreGetConfig()->isDefined("sound_device")) {
		bReturn = useDefaultDevice();
	}

	if ( !bReturn)	
	{
		bReturn = useFirstSoundDevice();
	}

	// Clear Errors
	ALenum error = AL_NO_ERROR;
	alGetError();

	// First create the JukeBox source
	alGenSources(2, &m_JukeBoxSource[0]);

	if (alGetError() != AL_NO_ERROR) {
		coreGetLog()->WriteError("No more free sources to create for JukeBox!",__FILE__,__LINE__);
	}

	// Generate Sources
	ALuint sourceID;	
	SourceObject* pSO;

	while (error == AL_NO_ERROR) {
		alGenSources(1, &sourceID);
		if ((error = alGetError()) == AL_NO_ERROR){
						
			pSO = new SourceObject();
			pSO->sourceID = sourceID;
			pSO->bInUse = false;
			pSO->source_sound = NULL;
	
			m_mSourceObjects[sourceID] = pSO;

			++m_iNumberOfSources;

			if (m_iNumberOfSources >= maxNumberOfSources) error = 1;
		} else {
			std::stringstream sstr;
			sstr << "No more free sources to create. Generated Sources are: " << m_iNumberOfSources;
			coreGetLog()->WriteWarning(sstr.str(), __FILE__, __LINE__);
			sstr.str("");
			sstr.clear();
		}
	}

	m_iFreeSources = m_iNumberOfSources;

	if (!m_b3D_ENABLED) {
		initListender2D();
	}

	return bReturn;
}

bool Audio_OpenAL::useDefaultDevice()
{
	m_DeviceName = coreGetConfig()->getString("sound_device").c_str();
	ALCdevice *pDevice = openSoundDevice( m_DeviceName);
	if (!pDevice)
		return false;
	else{
		if ( createContext( pDevice))
		{
			coreGetLog()->WriteLog("Using default sound device!",__FILE__,__LINE__);
			return true;
		}
		else
		{
			coreGetLog()->WriteError("alcOpenDevice using default sound device failed!",__FILE__,__LINE__);
			return false;
		}
	}
}

void Audio_OpenAL::getAllSoundDevices()
{
	// devices contains the device names, separated by NULL and terminated by two consecutive NULLs. 
	//return 
	const ALchar *allDevices = alcGetString(NULL, ALC_DEVICE_SPECIFIER);
	std::stringstream str;

	while (*allDevices != NULL) 
	{
		str << allDevices;

		m_vAllDeviceNames.push_back( str.str() );
		str.str("");
		str.clear();

		// das ist das Entscheidende!!!
		allDevices += strlen(allDevices) + 1;
	}
// Print the devices in debug mode
#ifdef _DEBUG 
	std::cout << "Number of Devices: " << m_vAllDeviceNames.size() << std::endl;
	for (unsigned int i = 0; i < m_vAllDeviceNames.size(); ++i)
	{
		std::cout << "Device[" << i << "]: " << m_vAllDeviceNames[i] << std::endl;
	}
#endif
}

std::vector<std::string> Audio_OpenAL::getDeviceList()
{
	return m_vAllDeviceNames;
}

std::string Audio_OpenAL::getCurrentDeviceName()
{
	return m_DeviceName;
}

bool Audio_OpenAL::useFirstSoundDevice()
{
	if ( m_vAllDeviceNames.size() > 0)
		m_DeviceName = m_vAllDeviceNames[0];//devices;
	else return false;

	if ( m_DeviceName. length() > 0)
	{		
		ALCdevice *device = openSoundDevice( m_DeviceName);
		if (!device)
			return false;
		else
		{
			if ( createContext( device))
			{
				// now, the new device name can be written into the config file:
				coreGetConfig()->setString("sound_device" , m_DeviceName);
				coreGetLog()->WriteLog("Using sound device first found!",__FILE__,__LINE__);
				return true;
			}
			else
			{
				coreGetConfig()->setString("sound_device" , m_DeviceName);
				coreGetLog()->WriteError("Creating a context on the opened sound device failed!",__FILE__,__LINE__);
				return false;
			}
		}
	}
	else
	{
		coreGetLog()->WriteError("No sound device available on this system!",__FILE__,__LINE__);
		return false;
	}
}


ALuint Audio_OpenAL::requestSource(AbstractSound_OpenAL* _sound, int _priority) {	
	// Return value
	ALuint sourceID = 0;

	std::map<ALuint, SourceObject*>::iterator it;
	it = m_mSourceObjects.begin();	
	
	// If there are free sources take the first source tha ist not in use	
	if (m_iFreeSources > 0) {		
		while (it != m_mSourceObjects.end()) {
			if (!it->second->bInUse) {
				
				// Decrease counter of free sounds
				--m_iFreeSources;				
				
				// Fill data-struct
				it->second->bInUse = true;							
				it->second->source_sound =_sound;
				
				// Set return-value
				sourceID = it->first;				
				break;
			}
			++it;
		}	
	} else if (m_iFreeSources == 0) {
		// If there are no free sources return the first with lower priority
		while (it != m_mSourceObjects.end()) {		
			
			if (it->second->source_sound) { 
				
				if (it->second->source_sound->getPriority() > _priority) {
					it->second->source_sound->stealSource(it->first);
				
					// Fill data-struct
					it->second->source_sound = _sound;

					// Set return-value
					sourceID = it->first;
					break;
				}			
			} else {
				// Fill data-struct
				it->second->source_sound =  _sound;					
				
				// Set return-value
				sourceID = it->first;
				break;
			}
			++it;
		}
	}
	
	if (sourceID == 0) return 0;

	ALint	iState;		
	alGetSourcei(sourceID, AL_SOURCE_STATE, &iState);
	if (iState == AL_PLAYING) {
				alSourceStop(sourceID);
	}
	
	// Unqueue queued buffers
	int iBuffersProcessed = 0;
	alGetSourcei(sourceID, AL_BUFFERS_PROCESSED, &iBuffersProcessed);
	
	ALuint tempBuffer;
	while (iBuffersProcessed) {
		alSourceUnqueueBuffers(sourceID, 1, &tempBuffer);
		--iBuffersProcessed;
	}

	// Detach buffer
	alSourcei(sourceID, AL_BUFFER, NULL);

	return sourceID;
}

ALCdevice* Audio_OpenAL::openSoundDevice( std::string name)
{
	return alcOpenDevice((ALCchar*)name.c_str());
}



bool Audio_OpenAL::createContext(ALCdevice* device)
{
	ALCcontext *context = alcCreateContext(device, NULL);
	if (context)
	{
		alcMakeContextCurrent(context);
		//todo: An Dennis Methode anpassen
		setDistanceModel();
		return true;
	} 
	else
	{// if the context creation fails
		coreGetLog()->WriteError("Creating a context on the opened sound device failed!",__FILE__,__LINE__);
		alcCloseDevice(device);			
		return false;
	}
}


// Depricated
bool Audio_OpenAL::changeDevice(std::string newDeviceName)
{
	bool bReturn = false;	

	if ( 1 == m_DeviceName.compare( newDeviceName)) // only then a change is needed
	{
		coreGetLog()->WriteLog("Change Audio Device to " + newDeviceName,__FILE__,__LINE__);

		// at first, clear the current context and device
		alcSuspendContext( m_pContext);
		alcDestroyContext( m_pContext);
		alcCloseDevice(m_pDevice);

		// and then initialize the new device ( and context)
		m_pDevice = alcOpenDevice((ALCchar*)newDeviceName.c_str());
		if (m_pDevice) 
		{
			m_pContext = alcCreateContext(m_pDevice, NULL);
			if (m_pContext) 
			{
				alcMakeContextCurrent(m_pContext);
				setDistanceModel();
				bReturn = true;
			} 
			else 
				alcCloseDevice(m_pDevice);			
		} 
		else 
		{
			coreGetLog()->WriteError("alcOpenDevice failed!",__FILE__,__LINE__);
			return false;
		}	
	}
	else {
		std::cout << "No sound device change is needed!" << std::endl;
		bReturn = true; // if the newDevice = the old Device
	}

	return bReturn;
}
	
void Audio_OpenAL::update(double dt) {
	static double cleanUp = 0;

	/*
	cleanUp += dt;
	if (cleanUp > 1) {
		cleanUp = 0;
		removeDeadSoundEffects();
	}*/
	
	// Sync Listener with camera
	if (m_b3D_ENABLED) {
		updateListener();
	}

	updateSources(dt);
	handleStopSounds(dt); // keep this order to avoid updating sounds that should be stopped!!!
	//handleDeleteSounds(dt);
	updateSoundsAndClearList(dt);	

	//STDCOUT(m_mISounds.size());
}

void Audio_OpenAL::setDistanceModel() {
	alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED);	
}

void  Audio_OpenAL::updateSources(const double &dt) {
	// Iterate through all sources and eihter (source is in use and playing)
	// call the update()-function of the corresponding ISound
	// or (source is in use but not playing) steal it from its ISound.

		
	// Rest free sources and count them
	m_iFreeSources = 0;

	std::map<ALuint, SourceObject*>::iterator it;
	it = m_mSourceObjects.begin();
	
	ALint iState;
	while (it != m_mSourceObjects.end()) {
				
		// Test for if it's playing
		alGetSourcei(it->first, AL_SOURCE_STATE, &iState);				
		if (iState == AL_PLAYING) {
				
			// Call the update()-method			
			if (it->second->source_sound) {
				m_SoundUpdateList[it->second->source_sound] = it->second->source_sound;				
			}
		} 
		
		if (iState == AL_STOPPED) { 
			
			// if not, release it 
			if (it->second->source_sound) {
				it->second->source_sound->stealSource(it->first);
				it->second->source_sound = NULL;
			}
			
			// mark as unused
			it->second->bInUse = false;
			
			// Count free sources
			++m_iFreeSources;
		
		}
				
		++it;
	}	
}

void Audio_OpenAL::updateSoundsAndClearList(const double &dt) {
	std::map<AbstractSound_OpenAL*,AbstractSound_OpenAL*>::iterator it;
	it = m_SoundUpdateList.begin();
	while (it != m_SoundUpdateList.end()) {
		it->second->update(dt);
		it = m_SoundUpdateList.erase(it);
	}
}

void Audio_OpenAL::handleStopSounds(double dt) {
	
	float gain = 0;
	std::map<ALuint,SourceObject*>::iterator iStop;
	std::map<ALuint,SourceObject*>::iterator iSource;
	
	iStop = m_SoundsToStop.begin();
	while (iStop != m_SoundsToStop.end()) {
		
		iSource = m_mSourceObjects.find(iStop->first);
		if (iSource != m_mSourceObjects.end() && iSource->first == iStop->first) {
			
			// Out of UpdateList!
			m_SoundUpdateList.erase(iStop->second->source_sound);
			
			alGetSourcef(iStop->first, AL_GAIN, &gain);
			gain += iStop->second->fFadeFactor * (float)dt; 

			if (gain <= 0) {
				alSourceStop(iStop->first);				
				DELETE_POINTER(iStop->second);
				iStop = m_SoundsToStop.erase(iStop);
			} else {
				alSourcef(iStop->first, AL_GAIN, gain);
				++iStop;
			}
		} else {
			DELETE_POINTER(iStop->second);
			iStop = m_SoundsToStop.erase(iStop);
		}		
	}
}


void Audio_OpenAL::handleDeleteSounds(double dt) {
	float gain = 0;
	std::map<ALuint,SourceObject*>::iterator iDelete;
	std::map<ALuint,SourceObject*>::iterator iSource;
	
	iDelete = m_SoundsToDestroy.begin();
	while (iDelete != m_SoundsToDestroy.end()) {
		
		iSource = m_mSourceObjects.find(iDelete->first);
		if (iSource != m_mSourceObjects.end() && iSource->first == iDelete->first) {
			
			// Out of UpdateList!
			m_SoundUpdateList.erase(iDelete->second->source_sound);
			
			alGetSourcef(iDelete->first, AL_GAIN, &gain);
			gain += iDelete->second->fFadeFactor * (float)dt; 

			if (gain <= 0) {
				alSourceStop(iDelete->first);				
				m_mISounds.erase(iSource->second->source_sound);
				iSource->second->bInUse = false;
				iSource->second->source_sound = NULL;
				DELETE_POINTER(iDelete->second->source_sound);
				DELETE_POINTER(iDelete->second);
				iDelete = m_SoundsToDestroy.erase(iDelete);				
			} else {
				alSourcef(iDelete->first, AL_GAIN, gain);
				++iDelete;
			}
		} else {
			m_mISounds.erase(iSource->second->source_sound);
			iSource->second->bInUse = false;
			iSource->second->source_sound = NULL;				
			DELETE_POINTER(iDelete->second->source_sound);
			DELETE_POINTER(iDelete->second);
			iDelete = m_SoundsToDestroy.erase(iDelete);			
		}		
	}	
}

void Audio_OpenAL::updateListener() {
	
	math::Vector3 vPos = coreGetRenderer()->getCamera()->getPosition();
	math::Vector3 vUp = coreGetRenderer()->getCamera()->getUpVec();
	math::Vector3 vView = coreGetRenderer()->getCamera()->getTarget();
	vView.normalize();

	float vec[6];

	vec[0] = vView.x;	//forward vector x value
	vec[1] = vView.y;	//forward vector y value
	vec[2] = -vView.z;	//forward vector z value
	vec[3] = vUp.x;		//up vector x value
	vec[4] = vUp.y;		//up vector y value
	vec[5] = vUp.z;		//up vector z value

	//set current listener position
	alListener3f(AL_POSITION, vPos.x, vPos.y, vPos.z);

	//set current listener orientation
	alListenerfv(AL_ORIENTATION, vec);
}

void Audio_OpenAL::initListender2D() {
	float vec[6];
	
	vec[0] = 0.0f;	//forward vector x value
	vec[1] = 0.0f;	//forward vector y value
	vec[2] = -1.0f;	//forward vector z value
	vec[3] = 0.0f;		//up vector x value
	vec[4] = 1.0f;		//up vector y value
	vec[5] = 0.0f;		//up vector z value

	//set current listener position to offSpring
	alListener3f(AL_POSITION, 0, 0, 0);

	//set current listener orientation
	alListenerfv(AL_ORIENTATION, vec);
}

audio::ISound* Audio_OpenAL::createISound(const std::string &sFileName) {
	
	audio::ISound* pSound;
	audio::ISoundBuffer* pSoundBuffer;

	// Get the ISoundBuffer
	pSoundBuffer = createSoundBufferFromFile(sFileName);
	
	// Test if buffer is loaded
	if (!pSoundBuffer) {
		return 0;
	}
	
	// Create SoundEffect
	pSound = new SoundEffect_OpenAL((SoundBuffer_OpenAL*)pSoundBuffer);
	
	// Test if ISound was created
	if (!pSound) {
		return NULL;
	}

	// Put into list of all ISounds
	m_mISounds[pSound] = pSound;

	return pSound;
}  


audio::ISoundBuffer* Audio_OpenAL::createSoundBufferFromFile(const std::string &fileName) {
	std::map<std::string, audio::ISoundBuffer*>::iterator it;
	it = m_mISoundBuffer.find(fileName);

	// Test if the soundfile is already loaded and return it!
	if (it != m_mISoundBuffer.end()) {
		return it->second;
	}

	// Or create it
	audio::ISoundBuffer* pSoundBuffer;	
	pSoundBuffer = new SoundBuffer_OpenAL(fileName);
	
	// Put it into the map of soundBuffers
	m_mISoundBuffer[fileName] = pSoundBuffer;

	return pSoundBuffer;
}


audio::ISound* Audio_OpenAL::createIStreamingSound(const std::string &fileName) {
	audio::ISound* pStreamingSound = NULL;
	audio::ISoundBuffer* pStreamingBuffer = NULL;

	// Get the IStreamingBuffer
	pStreamingBuffer = new StreamingBuffer_OpenAL(fileName);
	
	// Create SoundEffect
	pStreamingSound = new StreamingSound_OpenAL((StreamingBuffer_OpenAL*)pStreamingBuffer);

	// Put into list of all ISounds
	m_mISounds[pStreamingSound] = pStreamingSound;
	
	return pStreamingSound;
}


void Audio_OpenAL::destroyISound(audio::ISound* _pSound, float _fadeTime) {
		m_mISounds.erase(_pSound);

	
	// Check, if sound is in use (in m_SourceObjects)
	std::map<ALuint, SourceObject*>::iterator it;
	it = m_mSourceObjects.begin();
	while (it != m_mSourceObjects.end()) {		
		if (it->second->source_sound == _pSound) {
			alSourceStop(it->second->sourceID);
			it->second->bInUse = false;
			it->second->source_sound = NULL;
		}
		++it;		
	}

	AbstractSound_OpenAL* pAS = dynamic_cast<openAL::AbstractSound_OpenAL*>(_pSound);
	DELETE_POINTER(pAS);
	
	// New version for softfade out
	/*
	AbstractSound_OpenAL* pAS = dynamic_cast<openAL::AbstractSound_OpenAL*>(_pSound);
	bool bHasSource = false;
	
	// Check, if sound is in use (in m_SourceObjects)
	std::map<ALuint, SourceObject*>::iterator it;
	it = m_mSourceObjects.begin();
	while (it != m_mSourceObjects.end()) {		
		if (it->second->source_sound == _pSound) {
			bHasSource = true;
			
			SourceObject* pSO = new SourceObject();
			pSO->sourceID = it->second->sourceID;
			pSO->source_sound = pAS;
			
			if (_fadeTime >= 0)
				pSO->fFadeFactor = -1/_fadeTime;
			else
				pSO->fFadeFactor = -100;

			m_SoundsToDestroy[pSO->sourceID] = pSO;	
		}
		++it;		
	}
	
	if (!bHasSource) {	
		DELETE_POINTER(pAS);
	}*/
}


void Audio_OpenAL::stopISound(audio::ISound* _pSound, float _fFadeTime) {
	AbstractSound_OpenAL* pAS = dynamic_cast<openAL::AbstractSound_OpenAL*>(_pSound);

	// Check, if sound is in use (in m_SourceObjects)
	std::map<ALuint, SourceObject*>::iterator it;
	it = m_mSourceObjects.begin();
	while (it != m_mSourceObjects.end()) {		
		if (it->second->source_sound == _pSound) {
			
			SourceObject* pSO = new SourceObject();
			pSO->sourceID = it->second->sourceID;
			pSO->source_sound = pAS;
			pSO->fFadeFactor = -1/_fFadeTime;

			m_SoundsToStop[pSO->sourceID] = pSO;
		}
		++it;		
	}	
}

audio::IJukeBox* Audio_OpenAL::createIJukeBox() {
	
	static unsigned int currentFreeSource = 0;
	
	// Reached end
	if (sizeof(m_JukeBoxSource)/sizeof(m_JukeBoxSource[0]) <= currentFreeSource) {
		return NULL;
	}

	audio::IJukeBox* pJB = NULL;
	pJB = new JukeBox_OpenAL(m_JukeBoxSource[currentFreeSource]);

	++currentFreeSource;
		
	return pJB;
}

void Audio_OpenAL::destroyIJukeBox(audio::IJukeBox *_jb) {
	
}

} //namespace